package lb.io.lblet;

import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

import io.grpc.Server;
import io.grpc.netty.NettyServerBuilder;
import io.grpc.stub.StreamObserver;
import lb.io.api.system.v1.*;
import lb.io.apimachinery.pkg.apis.meta.v1.Address;
import lb.io.apimachinery.pkg.apis.meta.v1.ObjectMeta;
import lb.io.apimachinery.pkg.apis.meta.v1.PingOutPut;
import lb.io.apis.meta.v1.Namespace;
import lb.io.apis.meta.v1.ResourceStatus;
import lb.io.apiserver.pkg.service.pb.ChatResponse;
import lb.io.client.APIServerClient;
import lb.io.object.Resource;
import lb.io.object.Tool;
import lb.io.options.ClientOptions;
import lb.io.options.LetOptions;
import lb.io.options.SecureOptions;
import lb.io.utils.Klog;
import lb.io.utils.Strings;
import lb.io.utils.SystemUtil;
import org.apache.logging.log4j.Logger;

/**
 * 服务端
 */
public class LbLetServer {
    // 日志
    private static final Logger klog = Klog.getLogger(LbLetServer.class);

    // lblet 配置文件
    // private LetConfig config = new LetConfig(ClassLoader.getSystemResource("lblet.properties").getPath());
    private final LetOptions letOptions = new LetOptions();

    private final SecureOptions secureOptions = new SecureOptions();

    private final ClientOptions clientOptions = new ClientOptions();

    private Node node = null;

    private Component component;

    private Lblet lblet;

    private APIServerClient apiClient;

    // grpc server
    private Server server;

    final String version = "v2.2.257-aix";

    public LbLetServer(String config) throws IOException {
        InputStream input = new FileInputStream(config);
        Properties prop = new Properties();
        prop.load(input);
        secureOptions.setBindAddress(prop.getProperty("bind_address", "0.0.0.0"));
        secureOptions.setPort(Integer.parseInt(prop.getProperty("secure_port", "11403")));
        secureOptions.setMaxConnectionIdle(Long.parseLong(prop.getProperty("max_connection_idle", "30")));
        secureOptions.setMaxConnectionAge(Long.parseLong(prop.getProperty("max_connection_age", "0")));
        secureOptions.setMaxConnectionAgeGrace(Long.parseLong(prop.getProperty("max_connection_age_grace", "30")));
        secureOptions.setKeepAliveTime(Long.parseLong(prop.getProperty("server_keepalive_time", "10")));
        secureOptions.setKeepAliveTimout(Long.parseLong(prop.getProperty("server_keepalive_timeout", "15")));
        clientOptions.setName(prop.getProperty("name"));
        clientOptions.setEndpoints(prop.getProperty("endpoints"));
        clientOptions.setContentType(prop.getProperty("content_type", "application/json"));
        clientOptions.setTimeout(Integer.parseInt(prop.getProperty("conn_timeout", "3")));
        clientOptions.setBackStore(prop.getProperty("back_store", "/opt/howlink/linkBackup/store"));
        clientOptions.setKeepaliveDuration(Integer.parseInt(prop.getProperty("keepalive_duration", "10")));
        clientOptions.setKeepaliveTimeout(Integer.parseInt(prop.getProperty("keepalive_timeout", "15")));
        letOptions.setPointDir(prop.getProperty("point_dir", "/opt/howlink/linkBackup/touch"));
        letOptions.setScripts(prop.getProperty("scripts", "/opt/howink/linkBackup/resource/scripts"));
        letOptions.setModule(prop.getProperty("module", "oracle"));
        letOptions.setIQN(prop.getProperty("iqn"));
        input.close();
    }

    public void createServerChain() {
        defaultAPIClient();
        defaultContainer();
        defaultLet();
        defaultServer();
    }

    private void defaultAPIClient() {
        // 连接 APIServer
        apiClient = new APIServerClient(clientOptions);
    }

    private void defaultLet() {
        if (!letOptions.getModule().equals("oracle")) {
            klog.warn("AIX lblet 只支持 oracle");
            System.exit(1);
        }

        if (!new File(letOptions.getPointDir()).exists()) {
            new File(letOptions.getPointDir()).mkdir();
        }
    }

    private void defaultServer() {
        server = NettyServerBuilder.forPort(secureOptions.getPort())
                .maxConnectionIdle(secureOptions.getMaxConnectionIdle(), TimeUnit.SECONDS)
                .maxConnectionAge(9223372036854775807L, TimeUnit.NANOSECONDS)
                .maxConnectionAgeGrace(secureOptions.getMaxConnectionAgeGrace(), TimeUnit.SECONDS)
                .keepAliveTime(secureOptions.getKeepAliveTime(), TimeUnit.SECONDS)
                .keepAliveTimeout(secureOptions.getKeepAliveTimout(), TimeUnit.SECONDS)
                .permitKeepAliveTime(30, TimeUnit.SECONDS)
                .permitKeepAliveWithoutCalls(true)
                .maxInboundMessageSize(10485760) // 10M
                .maxInboundMessageSize(524288000) // 500M
                .addService(new Let(lblet, letOptions))
                .build();
    }

    public void run() throws IOException, InterruptedException {
        startChat();
        server.start();
        klog.info("Server started, listening on " + secureOptions.getPort());
        blockUntilShutdown();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.err.println("------shutting down lblet since JVM is shutting down-------");
                LbLetServer.this.stop();
                System.err.println("------lblet shut down------");
            }
        });
    }

    // stop 服务
    private void stop() {
        if (server != null) {
            server.shutdown();
        }
        if (apiClient != null) {
            apiClient.Close();
        }
    }

    // server 阻塞到程序退出
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    private void defaultContainer() {
        if (letOptions.getIQN().equals("")) {
            klog.warn("缺少 iscsi iqn 信息");
            System.exit(1);
        }

        // 获取 aix hostname
        String hostname = SystemUtil.hostname();
        // 获取 aix 系统序列号
        String serial = SystemUtil.serial();
        // 获取操作系统名称
        String machine = SystemUtil.machine();
        // 获取操作系统架构
        String processer = SystemUtil.processer();
        // 获取系统总内存大小
        long memory = SystemUtil.memory();
        PingOutPut pingOutPut = apiClient.pingz();
        Node node = Node.newBuilder()
                .setMetadata(Tool.objectMetaBuilder(Namespace.System, Strings.UUIDBuild(serial)).build())
                .setSpec(NodeSpec.newBuilder()
                        .setIp(pingOutPut.getRemoteIP())
                        .build())
                .setStatus(NodeStatus.newBuilder()
                        .setStatus(ResourceStatus.OKState.getStatus())
                        .setHostname(hostname)
                        .setOs(OS.newBuilder()
                                .setKernel(machine)
                                .setProcesser(processer)
                                .build())
                        .setMemory(Memory.newBuilder().setTotal(memory).build())
                        .build())
                .build();
        node = (Node) apiClient.GetOrCreate(Resource.NodeV1.getGvk(), node);

        Component component = Component.newBuilder()
                .setMetadata(Tool.objectMetaBuilder(Namespace.System, clientOptions.getName())
                        .putAnnotations("host", pingOutPut.getRemoteIP())
                        .putAnnotations("name", clientOptions.getName())
                        .putAnnotations("initiatorName", letOptions.getIQN())
                        .putAnnotations("mark", "lblet")
                        .build())
                .setSpec(ComponentSpec.newBuilder()
                        .setMark("lblet")
                        .setAddress(Address.newBuilder()
                                .setIp(pingOutPut.getRemoteIP())
                                .setPort(secureOptions.getPort())
                                .build())
                        .build())
                .setStatus(ResourceStatus.OKState.getStatus())
                .build();

        Lblet lblet = Lblet.newBuilder()
                .setMetadata(Tool.objectMetaBuilder(Namespace.System, clientOptions.getName()).build())
                .build();
        Object outLet = apiClient.GetObj(Resource.LbletV1.getGvk(), lblet);
        if (outLet == null) {
            Object outComponent = apiClient.TryPost(Resource.ComponentV1.getGvk(), component, 0);
            if (outComponent == null) {
                klog.fatal("create Component failed");
                System.exit(1);
            }

            component = (Component) outComponent;

            lblet = Lblet.newBuilder()
                    .setMetadata(Tool.objectMetaBuilder(Namespace.System, clientOptions.getName())
                            .addOwnerReferences(Tool.buildOwnerReference(Resource.ComponentV1.getGvk(), ((Component) outComponent).getMetadata()))
                            .addOwnerReferences(Tool.buildOwnerReference(Resource.NodeV1.getGvk(), node.getMetadata()))
                            .build())
                    .setSpec(LetSpec.newBuilder()
                            .setVersion(version)
                            .setIp(IPAddr.newBuilder()
                                    .setIp(pingOutPut.getRemoteIP())
                                    .setPort(secureOptions.getPort())
                                    .build())
                            .build())
                    .setStatus(LetStatus.newBuilder()
                            .setStatus(ResourceStatus.OKState.getStatus())
                            .setNode(node.getMetadata().getName())
                            .setMode("unregistered")
                            .setOs(OS.newBuilder()
                                    .setKernel(node.getStatus().getOs().getKernel())
                                    .setProcesser(node.getStatus().getOs().getProcesser())
                                    .build())
                            .build())
                    .build();
            outLet = apiClient.TryPost(Resource.LbletV1.getGvk(), lblet, 0);
            lblet = (Lblet) outLet;
        } else {
            Lblet remoteLet = (Lblet) outLet;
            if ((!remoteLet.getSpec().getIp().getIp().equals(pingOutPut.getRemoteIP())) ||
                    (remoteLet.getSpec().getIp().getPort() != secureOptions.getPort())) {
                klog.fatal("<Lblet:{}> is already exists", remoteLet.getMetadata().getName());
                System.exit(1);
            }
            lblet = remoteLet;
        }

        LetSpec letSpec = lblet.getSpec().toBuilder().setVersion(version).setModule(letOptions.getModule()).build();
        LetStatus letStatus = lblet.getStatus().toBuilder()
                .setInitiatorName(letOptions.getIQN())
                .setStatus(ResourceStatus.OKState.getStatus())
                .build();
        ObjectMeta letMeta = lblet.getMetadata().toBuilder().putLabels("host", pingOutPut.getRemoteIP()).build();
        lblet = lblet.toBuilder().setMetadata(letMeta).setSpec(letSpec).setStatus(letStatus).build();
        Object outNode = apiClient.TryReadyz(Resource.NodeV1.getGvk(), node, 0);
        if (outNode == null) {
            klog.fatal("ready node:{} failed", node.getMetadata().getName());
            return;
        }
        Object outComponent = apiClient.TryReadyz(Resource.ComponentV1.getGvk(), component, 0);
        if (outComponent == null) {
            klog.fatal("ready compoent:{} failed", component.getMetadata().getName());
            return;
        }
        outLet = apiClient.TryReadyz(Resource.LbletV1.getGvk(), lblet, 0);
        if (outLet == null) {
            klog.fatal("ready lblet:{} failed", lblet.getMetadata().getName());
            return;
        }

        this.node = (Node) outNode;
        setComponent((Component) outComponent);
        this.lblet = (Lblet) outLet;
    }

    private void startChat() {
        StreamObserver<ChatResponse> responseObserver = new StreamObserver<ChatResponse>() {
            @Override
            public void onNext(ChatResponse chatResponse) {

            }

            @Override
            public void onError(Throwable throwable) {

            }

            @Override
            public void onCompleted() {

            }
        };

        apiClient.Chat(getComponent(), responseObserver);
    }

    public synchronized Component getComponent() {
        return component;
    }

    public synchronized void setComponent(Component component) {
        this.component = component;
    }
}
